/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.meanzoft.dragonways.client;

import org.meanzoft.m3d.scene.camera.FreeCamera;
import java.awt.Font;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.lwjgl.Sys;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import org.meanzoft.m3d.InputManager;
import org.meanzoft.m3d.Viewport;
import org.meanzoft.m3d.font.GLFont;
import org.meanzoft.m3d.resource.ResourceManager;
import org.meanzoft.m3d.scene.Renderable;
import org.meanzoft.m3d.scene.SceneNode;
import org.meanzoft.minecraft_clone.map.Map;
import org.meanzoft.minecraft_clone.world.BlockCompiler;
import org.meanzoft.minecraft_clone.world.Chunk;
import org.meanzoft.minecraft_clone.world.WorldManager;
import static org.lwjgl.opengl.GL11.*;

/**
 *
 * @author Meanz
 */
public class Game {

    public static Game getGame() {
        return game;
    }
    /**
     * Core
     */
    public static boolean gameRunning;
    private Viewport gameScreen;
    private InputManager gameInputManager;
    private SceneNode gameRootNode;
    private SceneNode worldNode;
    private ResourceManager resourceManager;
    private WorldManager worldManager;
    private static Game game;
    private GLFont fontLarge;
    private GLFont fontMedium;
    private GLFont fontSmall;
    /**
     * Game objects
     */
    private Map map;
    private Human human;
    /**
     * Other vars
     */
    private int fps = 0;
    private int dFPS = 0;
    private long lastFrame = 0;
    private long lastFPS = 0;
    private float moveSpeed = 0.1f;
    private boolean isTyping = false;
    private String typingString = "";

    public Game() {

        /**
         * Initial Components
         */
        gameRunning = true;
        game = this;
        gameScreen = new Viewport();
        gameScreen.setDisplayMode(800, 640);
        gameScreen.create();
        gameScreen.setTitle("DragonWays Alpha");
        gameInputManager = new InputManager();
        gameInputManager.create();
        gameScreen.setCamera(new FreeCamera());
        gameRootNode = new SceneNode();
        worldNode = new SceneNode();
        worldManager = new WorldManager();
        gameRootNode.attach(worldNode);
        new BlockCompiler();

        /**
         * Resources
         */
        resourceManager = new ResourceManager();
        resourceManager.loadTexture("blocks", "./textures/blocks.png");
        resourceManager.loadTexture("sky_0", "./textures/sky_0.png");
        resourceManager.loadTexture("sky_1", "./textures/sky_1.png");
        resourceManager.loadTexture("sky_2", "./textures/sky_2.png");
        resourceManager.loadTexture("sky_3", "./textures/sky_3.png");
        resourceManager.loadTexture("sky_4", "./textures/sky_4.png");
        resourceManager.loadTexture("sky_5", "./textures/sky_5.png");

        fontLarge = new GLFont(new Font("Arial", Font.BOLD, 18));
        fontMedium = new GLFont(new Font("Arial", Font.BOLD, 14));
        fontSmall = new GLFont(new Font("Arial", Font.BOLD, 10));
        map = new Map();
        human = new Human();
        /**
         * Effects
         */
        //light.setParams(GL_AMBIENT, new Vector4f(0.5f, 0.5f, 0.5f, 1f));
        /**
         * Variable init
         */
        lastFPS = getTime(); // call before loop to initialise fps timer
        getDelta(); // call once before loop to initialise lastFrame

        /**
         * OpenGL Init
         */
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glClearDepth(1.0f);
        glMatrixMode(GL_MODELVIEW);
        glShadeModel(GL_SMOOTH);
        glDepthFunc(GL_LEQUAL);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_ALPHA_TEST);
        glEnable(GL_CULL_FACE);
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
        glEnable(GL_COLOR_MATERIAL);
        glAlphaFunc(GL_GREATER, 0.9f);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_BLEND);
        gameLoop();
    }

    public void gameLoop() {
        gameRunning = true;
        int TICKS_PER_SECOND = 60;
        int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
        int MAX_FRAMESKIP = 5;
        long next_game_tick = getTickCount();
        while (gameRunning) {
            try {
                int loops = 0;

                while (getTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
                    gameInput();
                    gameUpdate();

                    next_game_tick += SKIP_TICKS;
                    loops++;
                }

                gameRender();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        gameScreen.destroy();
        gameInputManager.destroy();
    }

    public long getTickCount() {
        return getTime();
    }
    int selBlockX = 0;
    int selBlockY = 0;
    int selBlockZ = 0;

    public void gameUpdate() {
        long t = getMS();
        updateFPS();
        updateNode(gameRootNode);
    }

    public void gameRender() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glLoadIdentity();

        glEnable3D();

       // ((ChaseCamera) gameScreen.getCamera()).setTarget(human.getPosition());
        //gameScreen.getCamera().setPosition(new Vector3f(
        //        human.getPosition().x + (3f * (float) Math.sin(Math.toRadians(human.yaw))),
        //        human.getPosition().y + 4f,
        //        human.getPosition().z - (3f * (float) Math.cos(Math.toRadians(human.yaw)))));
        gameScreen.getCamera().updateRender();
        /**
         * Apply World Objects
         */
        /**
         * Apply Blocks
         */
        
        glPushMatrix();
        {
            glEnable(GL_TEXTURE_2D);
            renderBlocks();
        }
        glPopMatrix();
        renderNode(gameRootNode);
        /**
         * Apply Moving Objects
         */
        /**
         * Apply Player
         */
        glPushMatrix();
        {
            glTranslatef(human.getPosition().x, human.getPosition().y, human.getPosition().z);
            glRotatef(human.getYaw(), 0f, 1f, 0f);
            //human.draw();
        }
        glPopMatrix();
        /**
         * GUI
         */
        glEnable2D();
        glColor3f(1f, 0f, 0f);
        glBegin(GL_LINES);
        {
            glVertex2f(gameScreen.getWidth() / 2, gameScreen.getHeight() / 2 - 10);
            glVertex2f(gameScreen.getWidth() / 2, gameScreen.getHeight() / 2 + 10);

            glVertex2f(gameScreen.getWidth() / 2 - 10, gameScreen.getHeight() / 2);
            glVertex2f(gameScreen.getWidth() / 2 + 10, gameScreen.getHeight() / 2);
        }
        glEnd();
        glDisable2D();
        glColor3f(1.0f, 1.0f, 1.0f);
        fontLarge.print(10, 620, "DragonWays Alpha v 0.0.1");
        fontMedium.print(10, 600, "FPS: " + dFPS);
        fontMedium.print(10, 580, "X: " + gameScreen.getCamera().getPosition().x);
        fontMedium.print(10, 560, "Y: " + gameScreen.getCamera().getPosition().y);
        fontMedium.print(10, 540, "Z: " + gameScreen.getCamera().getPosition().z);
        fontMedium.print(10, 520, "Num of chunks in queue: " + BlockCompiler.getBlockCompiler().getWorkSize());

        if (isTyping) {
            //fontMedium.print(10, 10, "Console: " + typingString + " |");
        } else {
            //fontMedium.print(10, 10, "Console: ");
        }
        /**
         * Send to screen
         */
        gameScreen.refresh();
    }

    public void gameInput() {
        gameInputManager.update();
        if (gameInputManager.isKeyDown(InputManager.KEY_ESCAPE)) {
            gameRunning = false;
            return;
        }
        /**
         * Camera Movement
         */
        gameScreen.getCamera().pitch(gameInputManager.getDY() * 0.2f);
        gameScreen.getCamera().yaw(gameInputManager.getDX() * 0.2f);
        //human.setYaw(-gameCamera.getYaw());
        if (gameInputManager.isKeyPressed(InputManager.KEY_END)) {
            isTyping = !isTyping;
        }
        if (!isTyping) {
            if (gameInputManager.isKeyDown(InputManager.KEY_W)) {
                gameScreen.getCamera().moveForward(moveSpeed);
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_S)) {
                gameScreen.getCamera().moveBackwards(moveSpeed);
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_A)) {
                gameScreen.getCamera().strafeLeft(moveSpeed);
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_D)) {
                gameScreen.getCamera().strafeRight(moveSpeed);
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_Q)) {
                gameScreen.getCamera().position.y += moveSpeed;
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_E)) {
                gameScreen.getCamera().position.y -= moveSpeed;
            }
        } else {
            if (gameInputManager.getPressedKeys().size() > 0) {
                if (gameInputManager.isKeyPressed(InputManager.KEY_BACK)) {
                    if (typingString.length() > 0) {
                        byte[] b = typingString.getBytes();
                        b[b.length - 1] = 0;
                        byte[] c = new byte[b.length - 1];
                        for (int i = 0; i < b.length - 1; i++) {
                            c[i] = b[i];
                        }
                        typingString = new String(c);
                    }
                }
                typingString += gameInputManager.getPressedText();
            }
        }
    }

    public void renderBlocks() {
        ResourceManager.getManager().getTexture("blocks").bind();
        for (Chunk c : WorldManager.getWorld().getChunks((int) human.getPosition().x, (int) human.getPosition().y, (int) human.getPosition().z, 12)) {
            if (c != null) {
                glPushMatrix();
                {
                    glTranslatef(c.getChunkX() * 16, c.getChunkY() * 16, c.getChunkZ() * 16);
                    if (c.isRequestingRebuild()) {
                        c.build();
                    } else {
                        c.draw();
                    }
                }
                glPopMatrix();
            }
        }
    }

    public void updateNode(SceneNode node) {
        //if (node instanceof SceneObject) {
        //    ((SceneObject) node).update();
        //}
        for (SceneNode children : node.getChildren()) {
            updateNode(children);
        }
    }

    public void renderNode(SceneNode node) {
        if (node instanceof Renderable) {
            glPushMatrix();
            {
                glMatrixMode(GL_MODELVIEW);
                glScalef(node.localScale.x, node.localScale.y, node.localScale.z);
                glRotatef(node.getPitch(), 1f, 0f, 0f);
                glRotatef(node.getYaw(), 0f, 1f, 0f);
                glTranslatef(node.position.x, node.position.y, node.position.z);
                ((Renderable) node).render();
            }
            glPopMatrix();
        } else {
            for (SceneNode children : node.getChildren()) {
                renderNode(children);
            }
        }
    }

    /**
     * Viewport functions
     */
    public void glEnable3D() {
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_ALPHA_TEST);
        glMatrixMode(GL_PROJECTION);
        {
            glLoadIdentity();
            GLU.gluPerspective(40f, 1f, 0.01f, 256f);
        }
        glMatrixMode(GL_MODELVIEW);
    }

    public void glEnable2D() {
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_ALPHA_TEST);
        glMatrixMode(GL_PROJECTION);
        {
            GL11.glPushMatrix();                   // preserve perspective view
            glLoadIdentity();
            glOrtho(0, gameScreen.getWidth(), gameScreen.getHeight(), 0, -500, 500);
        }
        glMatrixMode(GL_MODELVIEW);
        GL11.glPushMatrix();				   // Preserve the Modelview Matrix
        GL11.glLoadIdentity();				   // clear the Modelview Matrix
    }

    public void glDisable2D() {
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glPopMatrix();
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glPopMatrix();
        // turn Depth Testing back on
        GL11.glEnable(GL11.GL_DEPTH_TEST);
    }

    /**
     * Time functions
     */
    public int getDelta() {
        long time = getTime();
        int delta = (int) (time - lastFrame);
        lastFrame = time;
        return delta;
    }

    public long getMS() {
        return System.currentTimeMillis();
    }

    public long getTime() {
        return (Sys.getTime() * 1000) / Sys.getTimerResolution();
    }

    public void updateFPS() {
        if (getTime() - lastFPS > 1000) {
            dFPS = fps;
            fps = 0;
            lastFPS += 1000;
        }
        fps++;
    }
}
